Skip to content
This repository was archived by the owner on May 29, 2026. It is now read-only.

feat(adr-023): ActionAuthService skeleton + repair step + empty seed + unit tests#21

Merged
rubenvdlinde merged 2 commits into
developmentfrom
feat/adr-023-action-authorization
May 25, 2026
Merged

feat(adr-023): ActionAuthService skeleton + repair step + empty seed + unit tests#21
rubenvdlinde merged 2 commits into
developmentfrom
feat/adr-023-action-authorization

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Ports the ADR-023 action-authorization pattern from decidesk's reference implementation to the template so every new app picks it up for free.

What's included

  • lib/Service/ActionAuthService.php — the service (verbatim from decidesk@88844d0, namespace adjusted to OCA\AppTemplate).

    • requireAction(IUser, string) — throws OCSForbiddenException
    • can(IUser, string): bool — non-throwing variant
    • getAllowedGroups / getMatrix / setMatrix / getActions
    • Admin always passes (break-glass). Default-deny on missing or malformed matrix. Matrix stored in IAppConfig under actions key.
  • lib/Repair/InitializeActions.php — seeds lib/actions.seed.json on install; preserves admin-customized matrix on upgrade.

  • lib/actions.seed.json — empty actions object (template ships with no actions; implementers add one entry per requireAction() call site as the app grows).

  • tests/Unit/Service/ActionAuthServiceTest.php — 8 unit tests covering the contract (admin always passes, default-deny, group intersection, malformed JSON fallback, etc.).

  • appinfo/info.xmlInitializeActions registered on install + post-migration.

  • openspec/architecture/adr-023-action-authorization.md — ADR local to the app for reference.

How implementers use it

use OCA\AppTemplate\Service\ActionAuthService;
use OCP\AppFramework\OCS\OCSForbiddenException;

#[NoAdminRequired]
public function publish(int $id): JSONResponse {
    $user = $this->userSession->getUser();
    if ($user === null) {
        return new JSONResponse(['error' => 'Not authenticated'], 401);
    }
    try {
        $this->actionAuth->requireAction($user, 'item.publish');
    } catch (OCSForbiddenException $e) {
        return new JSONResponse(['error' => $e->getMessage()], 403);
    }
    // … do the work
}

Add "item.publish": ["admin"] to lib/actions.seed.json. Admin broadens via a settings UI (not included here — follow-up can port decidesk's SettingsController matrix endpoints).

Not included (follow-up material)

  • Settings-page UI to edit the matrix. Decidesk has get/setActions on SettingsController; left out of this initial port to keep the diff surgical.
  • Vue components for the matrix editor.

…+ 8 unit tests

Ports the ADR-023 action-authorization pattern from decidesk's reference
implementation (decidesk@88844d0) to the template so every new app picks
it up for free.

What's included:

* lib/Service/ActionAuthService.php — verbatim service:
  - requireAction (throws OCSForbiddenException)
  - can (non-throwing bool variant)
  - getAllowedGroups / getMatrix / setMatrix / getActions
  - Admin always passes (break-glass)
  - Default-deny on missing or malformed matrix
  - Matrix stored in IAppConfig under 'actions' key

* lib/Repair/InitializeActions.php — seeds lib/actions.seed.json on
  install; preserves admin-customized matrix on upgrade

* lib/actions.seed.json — empty "actions" object (template ships
  with no actions; implementers add one entry per requireAction()
  call site as the app grows)

* tests/Unit/Service/ActionAuthServiceTest.php — 8 unit tests
  covering the contract:
  1. Admin always passes
  2. Default-deny on missing matrix entry
  3. Admin-only matrix entry blocks non-admin
  4. Group-matching non-admin passes
  5. Admin in entry doesn't auto-pass non-admin
  6. can() returns bool
  7. Malformed matrix JSON falls back to deny
  8. getAllowedGroups defaults to ['admin']

* appinfo/info.xml — registered Repair\InitializeActions on install
  + post-migration

* openspec/architecture/adr-023-action-authorization.md — ADR copied
  from hydra for the app's local reference

Implementers declare an action name in their controller (e.g.
'item.publish'), add it to actions.seed.json with ['admin'] default,
then call $this->actionAuth->requireAction($user, 'item.publish')
wrapped in try/catch for OCSForbiddenException. Admin broadens the
group list via a settings UI (not included in this template port — a
follow-up change can port decidesk's SettingsController matrix API).

@SPEC openspec/architecture/adr-023-action-authorization.md
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/nextcloud-app-template @ a9683a0

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 100/100
npm ✅ 215/215
PHPUnit ⏭️
Newman ⏭️
Playwright ⏭️

Quality workflow — 2026-04-23 17:43 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde rubenvdlinde merged commit 5088cd1 into development May 25, 2026
4 of 18 checks passed
@rubenvdlinde rubenvdlinde deleted the feat/adr-023-action-authorization branch May 25, 2026 15:49
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/nextcloud-app-template @ ef3df11

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer
npm ❌ 2/684 denied
PHPUnit ⏭️
Newman ⏭️
Playwright ⏭️

❌ Denied npm licenses

Package Version License
pako 1.0.11 (MIT AND Zlib)
sha.js 2.4.12 (MIT AND BSD-3-Clause)

Quality workflow — 2026-05-25 15:50 UTC

Download the full PDF report from the workflow artifacts.

rubenvdlinde added a commit that referenced this pull request May 25, 2026
… populate example openspec (#61)

Consolidates the unique, still-unmerged work from the stale PRs #19, #20 and
#22 (their branches predated the manifest-renderer refactor and could not be
merged without reverting it), reapplied freshly on current development:

Headers / SPDX / @SPEC (was #19):
- Fix the propagated `dev@conductio.nl` -> `info@conduction.nl` typo across all
  10 template PHP files (root-cause fix; the typo had spread fleet-wide via
  scaffolds).
- Bump @copyright 2024 -> 2026 and add SPDX-FileCopyrightText /
  SPDX-License-Identifier inside the main docblock (after @license), matching
  the canonical format already used by lib/Mcp/ExampleToolProvider.php.
- Add the file-level @SPEC example to Application.php (ADR-003) so scaffolds
  show builders the convention.

Observability + REUSE (was #20):
- Add HealthController + MetricsController. appinfo/routes.php already declared
  metrics#index and health#index, but the classes were missing, so /api/health
  and /api/metrics returned 500. HealthController is public (@publicpage) and
  verifies OpenRegister; MetricsController is admin-only Prometheus text with
  {app}_info and {app}_health_status gauges (ADR-006).
- Add REUSE.toml for non-PHP source files.

Example OpenSpec (was #22):
- Populate openspec/changes/example-change/ + 7 openspec/specs/ capabilities
  that the @SPEC docblock tags point at, so the tags no longer resolve to dead
  links. Adapted task-5 / item-management / design.md to the current layout:
  the removed ItemController/ItemService demo is replaced by ActionAuthService
  (ADR-023, merged in #21) as the per-object-auth illustration.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant